﻿using System;

namespace ch06.binary.interpreter
{
    using byteOrder = LittleEndian;

    public static partial class Instr
    {
        private static void MemorySize(ref Vm vm, object args)
        {
            vm.OperandStack.pushU32(vm.Memory.Size());
        }

        private static void MemoryGrow(ref Vm vm, object args)
        {
            var oldSize = vm.Memory.Grow(vm.OperandStack.popU32());
            vm.OperandStack.pushU32(oldSize);
        }

        private static ulong GetOffset(ref Vm vm, object memArg)
        {
            var offset = ((MemArg) memArg).Offset;
            return (ulong) vm.OperandStack.popU32() + offset;
        }

        private static byte ReadU8(ref Vm vm, object memArg)
        {
            var buf = new byte[1];
            var offset = GetOffset(ref vm, memArg);
            vm.Memory.Read(offset, buf);
            return buf[0];
        }

        private static ushort ReadU16(ref Vm vm, object memArg)
        {
            var buf = new byte[2];
            var offset = GetOffset(ref vm, memArg);
            vm.Memory.Read(offset, buf);
            return byteOrder.Uint16(ref buf);
        }

        private static uint ReadU32(ref Vm vm, object memArg)
        {
            var buf = new byte [4];
            var offset = GetOffset(ref vm, memArg);
            vm.Memory.Read(offset, buf);
            return byteOrder.Uint32(ref buf);
        }

        private static ulong ReadU64(ref Vm vm, object memArg)
        {
            var buf = new byte[8];
            var offset = GetOffset(ref vm, memArg);
            vm.Memory.Read(offset, buf);
            return byteOrder.Uint64(ref buf);
        }

        // load
        private static void I32Load(ref Vm vm, object memArg)
        {
            var val = ReadU32(ref vm, memArg);
            vm.OperandStack.pushU32(val);
        }

        private static void I64Load(ref Vm vm, object memArg)
        {
            var val = ReadU64(ref vm, memArg);
            vm.OperandStack.PushU64(val);
        }

        private static void F32Load(ref Vm vm, object memArg)
        {
            var val = ReadU32(ref vm, memArg);
            vm.OperandStack.pushU32(val);
        }

        private static void F64Load(ref Vm vm, object memArg)
        {
            var val = ReadU64(ref vm, memArg);
            vm.OperandStack.PushU64(val);
        }

        private static void I32Load8S(ref Vm vm, object memArg)
        {
            var val = ReadU8(ref vm, memArg);
            vm.OperandStack.pushS32((int) ((sbyte) (val)));
        }

        private static void I32Load8U(ref Vm vm, object memArg)
        {
            var val = ReadU8(ref vm, memArg);
            vm.OperandStack.pushU32((uint) (val));
        }

        private static void I32Load16S(ref Vm vm, object memArg)
        {
            var val = ReadU16(ref vm, memArg);
            vm.OperandStack.pushS32((int) ((short) (val)));
        }

        private static void I32Load16U(ref Vm vm, object memArg)
        {
            var val = ReadU16(ref vm, memArg);
            vm.OperandStack.pushU32((uint) (val));
        }

        private static void I64Load8S(ref Vm vm, object memArg)
        {
            var val = ReadU8(ref vm, memArg);
            vm.OperandStack.PushS64((long) ((sbyte) (val)));
        }

        private static void I64Load8U(ref Vm vm, object memArg)
        {
            var val = ReadU8(ref vm, memArg);
            vm.OperandStack.PushU64((ulong) (val));
        }

        private static void I64Load16S(ref Vm vm, object memArg)
        {
            var val = ReadU16(ref vm, memArg);
            vm.OperandStack.PushS64((long) ((short) (val)));
        }

        private static void I64Load16U(ref Vm vm, object memArg)
        {
            var val = ReadU16(ref vm, memArg);
            vm.OperandStack.PushU64((ulong) (val));
        }

        private static void I64Load32S(ref Vm vm, object memArg)
        {
            var val = ReadU32(ref vm, memArg);
            vm.OperandStack.PushS64((long) (int) (val));
        }

        private static void I64Load32U(ref Vm vm, object memArg)
        {
            var val = ReadU32(ref vm, memArg);
            vm.OperandStack.PushU64((ulong) (val));
        }


        private static void WriteU8(ref Vm vm, object memArg, byte n)
        {
            var buf = new byte[1];
            buf[0] = n;
            var offset = GetOffset(ref vm, memArg);
            vm.Memory.Write(offset, buf);
        }

        private static void WriteU16(ref Vm vm, object memArg, UInt16 n)
        {
            var buf = new byte[2];
            byteOrder.PutUint16(ref buf, n);
            var offset = GetOffset(ref vm, memArg);
            vm.Memory.Write(offset, buf);
        }

        private static void WriteU32(ref Vm vm, object memArg, uint n)
        {
            var buf = new byte[4];
            byteOrder.PutUint32(ref buf, n);
            var offset = GetOffset(ref vm, memArg);
            vm.Memory.Write(offset, buf);
        }

        private static void WriteU64(ref Vm vm, object memArg, ulong n)
        {
            var buf = new byte[8];
            byteOrder.PutUint64(ref buf, n);
            var offset = GetOffset(ref vm, memArg);
            vm.Memory.Write(offset, buf);
        }

        // store
        private static void I32Store(ref Vm vm, object memArg)
        {
            var val = vm.OperandStack.popU32();
            WriteU32(ref vm, memArg, val);
        }

        private static void I64Store(ref Vm vm, object memArg)
        {
            var val = vm.OperandStack.PopU64();
            WriteU64(ref vm, memArg, val);
        }

        private static void F32Store(ref Vm vm, object memArg)
        {
            var val = vm.OperandStack.popU32();
            WriteU32(ref vm, memArg, val);
        }

        private static void F64Store(ref Vm vm, object memArg)
        {
            var val = vm.OperandStack.PopU64();
            WriteU64(ref vm, memArg, val);
        }

        private static void I32Store8(ref Vm vm, object memArg)
        {
            var val = vm.OperandStack.popU32();
            WriteU8(ref vm, memArg, (byte) (val));
        }

        private static void I32Store16(ref Vm vm, object memArg)
        {
            var val = vm.OperandStack.popU32();
            WriteU16(ref vm, memArg, (UInt16) (val));
        }

        private static void I64Store8(ref Vm vm, object memArg)
        {
            var val = vm.OperandStack.PopU64();
            WriteU8(ref vm, memArg, (byte) (val));
        }

        private static void I64Store16(ref Vm vm, object memArg)
        {
            var val = vm.OperandStack.PopU64();
            WriteU16(ref vm, memArg, (UInt16) (val));
        }

        private static void I64Store32(ref Vm vm, object memArg)
        {
            var val = vm.OperandStack.PopU64();
            WriteU32(ref vm, memArg, (uint) (val));
        }
    }
}